//
//  material.h
//  RayTracing
//
//  Created by Outro on 2019/12/25.
//  Copyright © 2019 Outro. All rights reserved.
//

#ifndef material_h
#define material_h

#include "hitable.h"

vec3 random_in_unit_sphere(){
    vec3 p;
    do{
    p = 2.0 * vec3(drand48(), drand48(), drand48()) - vec3(1.0, 1.0, 1.0);
    } while(p.square_length() >= 1.0);
    return p;
}

vec3 reflect(const vec3 &v, const vec3 &n){
    return v - 2 * dot(v, n) * n;
}


bool refract(const vec3 &v, const vec3 &n, float ni_over_nr, vec3 &refracted){
    vec3 uv = unit_vector(v);
    float dt = dot(uv, n);
    float discriminant = 1.0 - ni_over_nr*ni_over_nr*(1.0-dt*dt);
    if(discriminant > 0){
        refracted = ni_over_nr*(uv - n*dt) - n*sqrt(discriminant);
        return true;
    }
    else
        return false;
}

float schlick(float cosine, float ref_idx) {
    float r0 = (1-ref_idx) / (1+ref_idx);
    r0 = r0*r0;
    return r0 + (1-r0)*pow((1 - cosine),5);
}

class material {
public:
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const = 0;
};

class lambertian : public material{
public:
    vec3 albedo;
    
    lambertian(const vec3 & a) : albedo(a) {}
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
        vec3 target = rec.p + rec.normal + random_in_unit_sphere();
        scattered = ray(rec.p, target - rec.p);
        attenuation = albedo;
        return true;
    }
    
};

class metal : public material {
public:
    vec3 albedo;
    float fuzz;
    
    metal(const vec3 & a, float f) : albedo(a) {
        if(f < 1.0)
            fuzz = f;
        else
            fuzz = 1.0;
    }
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
        vec3 reflected = reflect(unit_vector(r_in.direction()), rec.normal);
        scattered = ray(rec.p, reflected + fuzz*random_in_unit_sphere());
        attenuation = albedo;
        return (dot(scattered.direction(), rec.normal) > 0);            
    }
};

class dielectric : public material{
public:
    float ref_idx;
    
    dielectric(float ri) : ref_idx(ri) {}
    virtual bool scatter(const ray& r_in, const hit_record& rec, vec3& attenuation, ray& scattered) const {
        vec3 out_normal;
        float ni_over_nr;
        attenuation = vec3(1.0, 1.0, 1.0);
        vec3 refracted;
        
        float reflect_prob;
        float cosine;
        
        if(dot(r_in.direction(), rec.normal) > 0.0){
            out_normal = -rec.normal;
            ni_over_nr = ref_idx;
            cosine = ref_idx * dot(r_in.direction(), rec.normal) / r_in.direction().length();
        }
        else {
            out_normal = rec.normal;
            ni_over_nr = 1.0 / ref_idx;
            cosine = -dot(r_in.direction(), rec.normal) / r_in.direction().length();
        }
        
        if(refract(r_in.direction(), out_normal, ni_over_nr, refracted)){
            reflect_prob = schlick(cosine, ref_idx);
        }
        else{
            reflect_prob = 1.0;
        }
        
        if (drand48() < reflect_prob) {
            vec3 reflected = reflect(r_in.direction(), rec.normal);
            scattered = ray(rec.p, reflected);
        }
        else {
            scattered = ray(rec.p, refracted);
        }
        
        return true;
    }
    
};
#endif /* material_h */
